package jcommand;

import java.awt.AWTException;
import java.awt.Robot;
import java.awt.event.KeyEvent;
import java.awt.Toolkit;
import java.io.IOException;

import jcommand.event.Event;

import jspeech.SpeechSpeaker;
import jspeech.exception.EngineConnectionException;
import jspeech.exception.EngineNotFoundException;

/**
 * This class is used to generate native system input events for apllicaition
 * that control of the mouse and keyboard is needed. The primary purpose of
 * Action is to provide the easiest and fastest way for developers to integrate
 * this application with Windows commands (keyboard, mouse...) <br>
 * <br>
 * This class are using <code>java.awt.Robot</code> to send the input event to
 * the computer.
 * 
 * @author Kenneth Lo
 * @see <a
 *      href="http://java.sun.com/javase/6/docs/api/java/awt/event/KeyEvent.html"
 *      target="_blank">java.awt.Event</a>
 * @see <a href="http://java.sun.com/javase/6/docs/api/java/awt/Robot.html"
 *      target="_blank">java.awt.Robot</a>
 * @since 1.0.0
 */
public class JavaGuiDevice {

	/**
	 * The event id which is generated by {@link jcommand.event.Event}.<br>
	 * This field must be init when create the object.
	 * 
	 * @since 1.0.0
	 */
	private final int event;

	/**
	 * Parameter for the event.<br>
	 * Key codes that have more than one physical key associated with them (e.g.
	 * KeyEvent.VK_SHIFT could mean either the left or right shift key) will map
	 * to the left key.
	 * 
	 * @see <a
	 *      href="http://java.sun.com/javase/6/docs/api/java/awt/event/KeyEvent.html"
	 *      target="_blank">java.awt.Event</a>
	 * @since 1.0.0
	 */
	private int keycode;

	/**
	 * Parameter for the event.<br>
	 * Use for the action <code>TypeWord</code><br>
	 * Use for the action <code>TTS</code>
	 * 
	 * @since 1.0.0
	 */
	private String word;

	/**
	 * Parameter for the event.<br>
	 * Use for the mouse event.
	 * 
	 * @since 1.0.0
	 */
	private int x;

	/**
	 * Parameter for the event.<br>
	 * Use for the mouse event.
	 * 
	 * @since 1.0.0
	 */
	private int y;

	/**
	 * Parameter for the sleep event. The period of time to sleep (in ms).
	 * 
	 * @since 1.0.0
	 */
	private long ms;

	/**
	 * The Robot object which coordinate system of the primary screen.
	 * 
	 * @since 1.0.0
	 */
	private Robot robot;

	/**
	 * Constructs a <code>Action</code> object to generate native system input
	 * events for apllicaition that control of the mouse and keyboard.
	 * 
	 * @param event
	 *            The event id which is generated by
	 *            {@link jcommand.event.Event}
	 * @throws AWTException
	 *             if the platform configuration does not allow low-level input
	 *             control. This exception is always thrown when
	 *             GraphicsEnvironment.isHeadless() returns true
	 * @see jcommand.event.Event
	 * @since 1.0.0
	 */
	public JavaGuiDevice(int event) throws AWTException {
		this.event = event;
		this.robot = new Robot();
		init();
	}

	/**
	 * Constructs a <code>Action</code> object to generate native system input
	 * events for apllicaition that control of the mouse and keyboard for given
	 * <code>keycode</code>
	 * 
	 * @param event
	 *            The event id which is generated by
	 *            {@link jcommand.event.Event}
	 * @param keycode
	 *            keycode - Key to press (e.g. KeyEvent.VK_A)
	 * @throws AWTException
	 *             if the platform configuration does not allow low-level input
	 *             control. This exception is always thrown when
	 *             GraphicsEnvironment.isHeadless() returns true
	 * @see <a
	 *      href="http://java.sun.com/javase/6/docs/api/java/awt/event/KeyEvent.html"
	 *      target="_blank">java.awt.Event</a>
	 * @see jcommand.event.Event
	 * @since 1.0.0
	 */
	public JavaGuiDevice(int event, int keycode) throws AWTException {
		this(event);
		this.keycode = keycode;
	}

	/**
	 * Constructs a <code>Action</code> object to sleep for a specific period
	 * of time in ms
	 * 
	 * @param event
	 *            The event id which is generated by
	 *            {@link jcommand.event.Event}
	 * @param ms
	 *            the length of time to sleep in milliseconds.
	 * 
	 * @throws AWTException
	 *             if the platform configuration does not allow low-level input
	 *             control. This exception is always thrown when
	 *             GraphicsEnvironment.isHeadless() returns true
	 * @see <a
	 *      href="http://java.sun.com/javase/6/docs/api/java/awt/event/KeyEvent.html"
	 *      target="_blank">java.awt.Event</a>
	 * @see jcommand.event.Event
	 * @since 1.0.0
	 */
	public JavaGuiDevice(int event, long ms) throws AWTException {
		this(event);
		this.ms = ms;
	}

	/**
	 * Constructs a <code>Action</code> object to generate native system input
	 * events for apllicaition that control of the mouse and keyboard for given
	 * <code>word</code>
	 * 
	 * @param event
	 *            The event id which is generated by
	 *            {@link jcommand.event.Event}
	 * @param word
	 *            Word the need to be typed by the <code>TypeWord()</code>
	 *            method
	 * @throws AWTException
	 *             if the platform configuration does not allow low-level input
	 *             control. This exception is always thrown when
	 *             GraphicsEnvironment.isHeadless() returns true
	 * 
	 * @see jcommand.event.Event
	 * 
	 * @since 1.0.0
	 */
	public JavaGuiDevice(int event, String word) throws AWTException {
		this(event);
		this.word = word;
	}

	/**
	 * Constructs a <code>Action</code> object to generate native system input
	 * events for apllicaition that control of the mouse and keyboard for given
	 * <code>x</code> and <code>y</code>
	 * 
	 * @param event
	 *            The event id which is generated by
	 *            {@link jcommand.event.Event}
	 * @param x
	 *            Use for mouse event (e.g. X-coordinate)
	 * @param y
	 *            Use for mouse event (e.g. Y-coordinate)
	 * @throws AWTException
	 *             if the platform configuration does not allow low-level input
	 *             control. This exception is always thrown when
	 *             GraphicsEnvironment.isHeadless() returns true
	 * 
	 * @see jcommand.event.Event
	 * 
	 * @since 1.0.0
	 */
	public JavaGuiDevice(int event, int x, int y) throws AWTException {
		this(event);
		this.x = x;
		this.y = y;
	}

	/**
	 * Presses a given key. The key should be released using the keyRelease
	 * method.
	 * 
	 * Key codes that have more than one physical key associated with them (e.g.
	 * KeyEvent.VK_SHIFT could mean either the left or right shift key) will map
	 * to the left key.
	 * 
	 * @param keycode
	 *            Key to press (e.g. KeyEvent.VK_A)
	 * @return <code>Action</code> object with <code>KeyPress</code> event
	 * @throws AWTException
	 *             if the platform configuration does not allow low-level input
	 *             control. This exception is always thrown when
	 *             GraphicsEnvironment.isHeadless() returns true
	 * @see <a
	 *      href="http://java.sun.com/javase/6/docs/api/java/awt/event/KeyEvent.html"
	 *      target="_blank">java.awt.Event</a>
	 * 
	 * @since 1.0.0
	 */
	public static JavaGuiDevice KeyPress(int keycode) throws AWTException {
		return new JavaGuiDevice(Event.KeyPress, keycode);
	}

	/**
	 * Releases a given key.
	 * 
	 * Key codes that have more than one physical key associated with them (e.g.
	 * KeyEvent.VK_SHIFT could mean either the left or right shift key) will map
	 * to the left key.
	 * 
	 * @param keycode
	 *            Key to release (e.g. KeyEvent.VK_A)
	 * @return <code>Action</code> object with <code>KeyRelease</code> event
	 * @throws AWTException
	 *             if the platform configuration does not allow low-level input
	 *             control. This exception is always thrown when
	 *             GraphicsEnvironment.isHeadless() returns true
	 * @see <a
	 *      href="http://java.sun.com/javase/6/docs/api/java/awt/event/KeyEvent.html"
	 *      target="_blank">java.awt.Event</a>
	 * @since 1.0.0
	 */
	public static JavaGuiDevice KeyRelease(int keycode) throws AWTException {
		return new JavaGuiDevice(Event.KeyRelease, keycode);
	}

	/**
	 * Moves mouse pointer to given screen coordinates.
	 * 
	 * @param x
	 *            X position
	 * @param y
	 *            Y position
	 * @return <code>Action</code> object with <code>MouseMove</code> event
	 * @throws AWTException
	 *             if the platform configuration does not allow low-level input
	 *             control. This exception is always thrown when
	 *             GraphicsEnvironment.isHeadless() returns true
	 * 
	 * @since 1.0.0
	 */
	public static JavaGuiDevice MouseMove(int x, int y) throws AWTException {
		return new JavaGuiDevice(Event.KeyPress, x, y);
	}

	/**
	 * Presses one or more mouse buttons. The mouse buttons should be released
	 * using the mouseRelease method.
	 * 
	 * @param buttons
	 *            the Button mask; a combination of one or more of these flags:
	 *            <li>InputEvent.BUTTON1_MASK
	 *            <li>InputEvent.BUTTON2_MASK
	 *            <li>InputEvent.BUTTON3_MASK
	 * @return <code>Action</code> object with <code>MousePress</code> event
	 * @throws AWTException
	 *             if the platform configuration does not allow low-level input
	 *             control. This exception is always thrown when
	 *             GraphicsEnvironment.isHeadless() returns true
	 * @see <a
	 *      href="http://java.sun.com/javase/6/docs/api/java/awt/event/InputEvent.html"
	 *      target="_blank">java.awt.event.InputEvent</a>
	 * 
	 * @since 1.0.0
	 */
	public static JavaGuiDevice MousePress(int buttons) throws AWTException {
		return new JavaGuiDevice(Event.MousePress, buttons);
	}

	/**
	 * Releases one or more mouse buttons.
	 * 
	 * @param buttons
	 *            the Button mask; a combination of one or more of these flags:
	 *            <li>InputEvent.BUTTON1_MASK
	 *            <li>InputEvent.BUTTON2_MASK
	 *            <li>InputEvent.BUTTON3_MASK
	 * @return <code>Action</code> object with <code>MouseRelease</code>
	 *         event
	 * @throws AWTException
	 *             if the platform configuration does not allow low-level input
	 *             control. This exception is always thrown when
	 *             GraphicsEnvironment.isHeadless() returns true
	 * @see <a
	 *      href="http://java.sun.com/javase/6/docs/api/java/awt/event/InputEvent.html"
	 *      target="_blank">java.awt.event.InputEvent</a>
	 * 
	 * @since 1.0.0
	 */
	public static JavaGuiDevice MouseRelease(int buttons) throws AWTException {
		return new JavaGuiDevice(Event.MouseRelease, buttons);
	}

	/**
	 * Click a given key. This action included the following:
	 * <li> KeyPress - Press the Key
	 * <li> KeyRelease - Release the pressed key
	 * 
	 * Key codes that have more than one physical key associated with them (e.g.
	 * KeyEvent.VK_SHIFT could mean either the left or right shift key) will map
	 * to the left key.
	 * 
	 * @param keycode
	 *            Key to click (e.g. KeyEvent.VK_A)
	 * @return <code>Action</code> object with <code>KeyClick</code> event
	 * @throws AWTException
	 *             if the platform configuration does not allow low-level input
	 *             control. This exception is always thrown when
	 *             GraphicsEnvironment.isHeadless() returns true
	 * @see <a
	 *      href="http://java.sun.com/javase/6/docs/api/java/awt/event/Event.html"
	 *      target="_blank">java.awt.event.Event</a>
	 * 
	 * @since 1.0.0
	 */
	public static JavaGuiDevice KeyClick(int keycode) throws AWTException {
		return new JavaGuiDevice(Event.KeyClick, keycode);
	}

	/**
	 * Type a word with using the keyboard. This method simplify the work flow
	 * and no need to developers to consider <code>keyPress</code> and
	 * <code>ketRelease</code> event.
	 * 
	 * @param word
	 *            Word to type
	 * @return <code>Action</code> object with <code>TypeWord</code> event
	 * @throws AWTException
	 *             if the platform configuration does not allow low-level input
	 *             control. This exception is always thrown when
	 *             GraphicsEnvironment.isHeadless() returns true
	 * 
	 * @since 1.0.0
	 */
	public static JavaGuiDevice TypeWord(String word) throws AWTException {
		return new JavaGuiDevice(Event.TypeWord, word);
	}

	/**
	 * Speck out the word. It is implement by using JSpeech API
	 * 
	 * @param word
	 *            Word to speck
	 * @return <code>Action</code> object with <code>Test to Speech</code>
	 *         event
	 * @throws AWTException
	 * 
	 * @since 1.0.0
	 */
	public static JavaGuiDevice TTS(String word) throws AWTException {
		return new JavaGuiDevice(Event.TTS, word);
	}

	/**
	 * Hold one key and press the other key and release the held key
	 * 
	 * @param holdKey
	 *            Key to hole (e.g. KeyEvent.VK_CTRL)
	 * @param clickKey
	 *            Key to press (e.g. KeyEvent.VK_F4)
	 * @return <code>Action</code> object with <code>KeyHoldAndClick</code>
	 *         event
	 * @throws AWTException
	 *             if the platform configuration does not allow low-level input
	 *             control. This exception is always thrown when
	 *             GraphicsEnvironment.isHeadless() returns true
	 * @see <a
	 *      href="http://java.sun.com/javase/6/docs/api/java/awt/event/Event.html"
	 *      target="_blank">java.awt.event.Event</a>
	 * 
	 * @since 1.0.0
	 */
	public static JavaGuiDevice KeyHoldAndClick(int holdKey, int clickKey)
			throws AWTException {
		return new JavaGuiDevice(Event.KeyHoldAndClick, holdKey, clickKey);
	}

	/**
	 * Launch an applicaiton. The application can be chosen by
	 * {@link jcommand.event.Application}
	 * 
	 * @param applicationKey
	 * @return <code>Action</code> object with <code>Launch</code> event
	 * @throws AWTException
	 *             if the platform configuration does not allow low-level input
	 *             control. This exception is always thrown when
	 *             GraphicsEnvironment.isHeadless() returns true
	 * @see jcommand.event.Application
	 * 
	 * @since 1.0.0
	 */
	public static JavaGuiDevice Launch(String applicationKey) throws AWTException {
		return new JavaGuiDevice(Event.Launch, applicationKey);
	}

	/**
	 * Press and Release one or more mouse buttons.
	 * 
	 * @param buttons
	 *            the Button mask; a combination of one or more of these flags:
	 *            <li>InputEvent.BUTTON1_MASK
	 *            <li>InputEvent.BUTTON2_MASK
	 *            <li>InputEvent.BUTTON3_MASK
	 * @return <code>Action</code> object with <code>MouseClick</code> event
	 * @throws AWTException
	 *             if the platform configuration does not allow low-level input
	 *             control. This exception is always thrown when
	 *             GraphicsEnvironment.isHeadless() returns true
	 * @see <a
	 *      href="http://java.sun.com/javase/6/docs/api/java/awt/event/InputEvent.html"
	 *      target="_blank">java.awt.event.InputEvent</a>
	 * 
	 * @since 1.0.0
	 */
	public static JavaGuiDevice MouseClick(int buttons) throws AWTException {
		return new JavaGuiDevice(Event.MouseClick, buttons);
	}

	/**
	 * Sleep for a specific of time.
	 * 
	 * @param ms
	 *            period of time for sleeping (in ms).
	 * @return <code>Action</code> object with <code>Sleep</code> event
	 * @throws AWTException
	 * @since 1.0.0
	 */
	public static JavaGuiDevice Sleep(long ms) throws AWTException {
		return new JavaGuiDevice(Event.MouseClick, ms);
	}

	/**
	 * Execute the action.
	 * 
	 * @throws IOException
	 * @since 1.0.0
	 */
	public void run() throws IOException {

		if (event == Event.KeyPress)
			robot.keyPress(keycode);
		else if (event == Event.KeyRelease)
			robot.keyRelease(keycode);
		else if (event == Event.MouseMove)
			robot.mouseMove(x, y);
		else if (event == Event.MousePress)
			robot.mousePress(keycode);
		else if (event == Event.MouseRelease)
			robot.mouseRelease(keycode);
		else if (event == Event.KeyClick) {
			click(keycode);
		} else if (event == Event.TypeWord) {
			for (int i = 0; i < word.length(); i++) {

				int key = (int) word.charAt(i);

				if (key >= 97) {
					key -= 32;
					if (Toolkit.getDefaultToolkit().getLockingKeyState(
							KeyEvent.VK_CAPS_LOCK))
						holdAndClick(KeyEvent.VK_SHIFT, key);
					else
						click(key);

				} else {
					if (!Toolkit.getDefaultToolkit().getLockingKeyState(
							KeyEvent.VK_CAPS_LOCK))
						holdAndClick(KeyEvent.VK_SHIFT, key);
					else
						click(key);

				}
			}
		} else if (event == Event.KeyHoldAndClick)
			holdAndClick(x, y);
		else if (event == Event.Launch)
			Runtime.getRuntime().exec(word);
		else if (event == Event.MouseClick) {
			robot.mousePress(keycode);
			robot.mouseRelease(keycode);
		} else if (event == Event.Sleep) {
			try {
				Thread.sleep(ms);
			} catch (InterruptedException e) {
			}
		} else if (event == Event.TTS) {
			SpeechSpeaker speaker = new SpeechSpeaker(56789);
			try {
				speaker.start();
			} catch (EngineNotFoundException e) {

			} catch (EngineConnectionException e) {

			}
			speaker.TTS(word);
			speaker.close();
		}

	}

	/**
	 * Init the robot object. This is set <code>AutoWaitForIdle</code> to
	 * true.
	 * 
	 * @see <a href="http://java.sun.com/javase/6/docs/api/java/awt/Robot.html"
	 *      target="_blank">java.awt.Robot</a>
	 * 
	 * @since 1.0.0
	 */
	private void init() {
		robot.setAutoWaitForIdle(true);
	}

	/**
	 * Click a given key. This action included the following:
	 * <li> KeyPress - Press the Key
	 * <li> KeyRelease - Release the pressed key
	 * 
	 * Key codes that have more than one physical key associated with them (e.g.
	 * KeyEvent.VK_SHIFT could mean either the left or right shift key) will map
	 * to the left key.
	 * 
	 * @param keycode
	 *            Key to click (e.g. KeyEvent.VK_A)
	 * 
	 * @since 1.0.0
	 */
	private void click(int keycode) {
		robot.keyPress(keycode);
		robot.keyRelease(keycode);
	}

	/**
	 * Hold one key and press the other key and release the held key
	 * 
	 * @param holdKey
	 *            Key to hole (e.g. KeyEvent.VK_CTRL)
	 * @param clickKey
	 *            Key to press (e.g. KeyEvent.VK_F4)
	 * 
	 * @since 1.0.0
	 */
	private void holdAndClick(int holdKey, int clickKey) {
		robot.keyPress(holdKey);
		click(clickKey);
		robot.keyRelease(holdKey);
	}
}
